#include "stdafx.h"
#include "jniEnvironment.h"
#include "java.h"
#include "jniString.h"
#include "jniReference.h"
#include "customException.h"
#include <cassert>
#include <process.h>

JavaVM* sJavaVM = 0;
bool sJvmMainThreadRunning = false;
std::string sOpenAgentHome;
std::string sNoDepsJar;

void InitializeDirectories()
{
  sOpenAgentHome = getenv("OPEN_AGENT_HOME");
  sNoDepsJar = "-Djava.class.path=" + sOpenAgentHome + "/JTF/silktest-jtf-nodeps.jar";
  std::string path = "PATH=" + 
    sOpenAgentHome + "/jre/bin;" +
    sOpenAgentHome + "/jre/bin/client;" +
    getenv("PATH"); 
  _putenv(path.c_str());
}

unsigned int __stdcall JvmMainThread(void* params)
{
  InitializeDirectories();

  JavaVMInitArgs vm_args;
  const int OPTION_COUNT = 1;
  JavaVMOption options[OPTION_COUNT];
  // classpath
  options[0].optionString = (char*)sNoDepsJar.c_str();
  //options[1].optionString = "-Xdebug";    
  //options[2].optionString = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12345";

  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = OPTION_COUNT;
  vm_args.ignoreUnrecognized = TRUE;

  //Create JVM
  JNIEnv* pMainEnvironment = 0;
  jint success = JNI_CreateJavaVM(&sJavaVM, (void**)&pMainEnvironment, &vm_args);

  sJvmMainThreadRunning = true;

  while (sJvmMainThreadRunning)
  {
    Sleep(100);
  }

  sJavaVM->DestroyJavaVM();
  sJavaVM = 0;
  return 0;
}

void LoadJvm()
{
  if (sJvmMainThreadRunning)
    return;
  _beginthreadex(0, 0, &JvmMainThread, 0, 0, 0);
  while (!sJvmMainThreadRunning)
  {
    Sleep(100);
  }
}

void UnloadJvm()
{
  sJvmMainThreadRunning = false;
  while (sJavaVM)
  {
    Sleep(100);
  }
}

bool IsJvmRunning()
{
  return sJavaVM;
}

CJniEnvironment::CJniEnvironment() : env(0), detach(false)
{
  // ensure jvm is loaded
  LoadJvm();
  // get environment or attach current thread
  jint result = sJavaVM->GetEnv((void**)&env, JNI_VERSION_1_2);
  if (result == JNI_EDETACHED)
  {
    sJavaVM->AttachCurrentThread((void**)&env, 0);
    detach = true;
  }
}

CJniEnvironment::~CJniEnvironment()
{
  DoExceptionHandling();
  if (detach)
  {
    sJavaVM->DetachCurrentThread();
  }
}

JNIEnv* CJniEnvironment::Env()
{
  DoExceptionHandling();
  return env; 
}

void CJniEnvironment::DoExceptionHandling(int code, wchar_t* source)
{
  if (env->ExceptionCheck() != JNI_FALSE)
  {
    std::wstring description = GetExceptionMessage();
    throw CustomException(code, source, description);
  }
}

std::wstring CJniEnvironment::GetExceptionMessage()
{
  CJniLocalReference<jthrowable> throwable = env->ExceptionOccurred();
  if (throwable)
  {
    // clear the exception first
    env->ExceptionClear();
    
    // try to retrieve the message
    JavaThrowableClass javaThrowable;
    CJniString message = javaThrowable.GetMessage.Invoke(throwable.Get());
    if (message)
      return message.WString();
    
    // try to find a cause with a message
    JavaExceptionClass javaException;
    CJniLocalReference<jobject> cause = javaException.GetCause.Invoke(throwable.Get());
    int depth = 0;
    while (cause && depth++ < 10)
    {
      CJniString message = javaThrowable.GetMessage.Invoke(cause.Get());
      if (message)
        return message.WString();
      cause.Attach(javaException.GetCause.Invoke(cause.Get()));
    }
    // clear possible exception from iterating through causes
    env->ExceptionClear();  
    
    // use the throwables toString() method as message
    JavaObjectClass javaObject;
    CJniString str = javaObject.ToString.Invoke(throwable.Get());
      return str.WString();
  }
  return L"";
}